﻿using System;
using System.ComponentModel;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.Regions;
using Tanmia.Basis.Concretes;
using Tanmia.Basis.Core;
using Tanmia.Basis.Data;
using Tanmia.Basis.Domain;
using Tanmia.Basis.Prism;
using Tanmia.Basis.Properties;
using Tanmia.Basis.Security;
using Tanmia.Basis.ServiceInterface;
using Tanmia.Basis.ViewInterface;

namespace Tanmia.Basis.ViewModel
{
    /// <summary>
    /// Represent the base view model class, for each editor screen.
    /// </summary>
    /// <typeparam name="TCore">Any business entity class which is derived from the BaseBusinessObject.</typeparam>
    public class EditorViewModel<TCore> : BaseViewModel, IDataErrorInfo
        where TCore : BaseBusinessObject, new()
    {
        private TCore entity;

        #region Protected Properties

        /// <summary>
        /// Gets domain object of the Entity object.
        /// </summary>
        protected BusinessDomain<TCore> EntityObj { get; private set; }

        /// <summary>
        /// Gets a reference to the user messaging service.
        /// </summary>
        protected IMessageService MessageService { get; private set; }

        /// <summary>
        /// Gets a reference to the RegionManager service.
        /// </summary>
        protected IRegionManager RegionManager { get; private set; }

        /// <summary>
        /// Gets a reference to the EventAggregator service.
        /// </summary>
        protected IEventAggregator EventAggregator { get; private set; }

        /// <summary>
        /// Gets a reference to the EntityNavigator Service.
        /// </summary>
        protected IBusinessEntityNavigator<TCore> EntityNavigator { get; private set; }

        /// <summary>
        /// Gets string message confirmation to be shown to the user in the Delete Confirmation message box.
        /// </summary>
        protected virtual string DeleteConfirmationMessage
        {
            get { return "Are you sure to delete? "; }
        }

        #endregion Protected Properties

        #region Public Properties

        /// <summary>
        /// Gets the command to invoke when (New) button is pressed.
        /// </summary>
        public ICommand NewCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (Save) button is pressed.
        /// </summary>
        public ICommand SaveCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (Save And New) button is pressed.
        /// </summary>
        public ICommand SaveAndNewCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (Save And Close) button is pressed.
        /// </summary>
        public ICommand SaveAndCloseCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (Delete) button is pressed.
        /// </summary>
        public ICommand DeleteCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (MoveToFirst) button is pressed.
        /// </summary>
        public ICommand MoveToFirstCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (MoveToLast) button is pressed.
        /// </summary>
        public ICommand MoveToLastCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (MoveToNext) button is pressed.
        /// </summary>
        public ICommand MoveToNextCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (MoveToPrevious) button is pressed.
        /// </summary>
        public ICommand MoveToPreviousCommand { get; protected set; }

        /// <summary>
        /// Gets human readable name of the business entity.
        /// </summary>
        public string EntityName { get; protected set; }

        /// <summary>
        /// Gets or Sets the business entity which is currently manipulated by Editor Window.
        /// </summary>
        public TCore Entity
        {
            get { return entity; }
            set
            {
                if (entity != null)
                    entity.PropertyChanged -= Entity_PropertyChanged;

                entity = value;
                entity.PropertyChanged += Entity_PropertyChanged;
                entity.Capture();
                EntityObj.CloseSession();

                RaisePropertyChanged("Entity");
            }
        }

        #endregion Public Properties

        public EditorViewModel(IShellView shellView, IRegionManager regionManager, IEventAggregator eventAggregator, IBusinessEntityNavigator<TCore> entityNavigator, IMessageService messageService, BusinessDomain<TCore> domainObject)
            : base(shellView)
        {
            this.RegionManager = regionManager;
            this.MessageService = messageService;
            this.EntityObj = domainObject;
            this.EventAggregator = eventAggregator;
            this.EntityNavigator = entityNavigator;

            // x. Command Creation
            NewCommand = new UICommand(OnNew, ModifierKeys.Control, Key.N) { Caption = "جديد", Order = 1, Group = 1 };
            SaveCommand = new UICommand(OnSave, OnCanSave, ModifierKeys.Control, Key.S) { Caption = "حفظ", Order = 2, Group = 1 };
            SaveAndNewCommand = new UICommand(OnSaveAndNew, OnCanSave, ModifierKeys.Control | ModifierKeys.Shift, Key.S) { Caption = "حفظ وجديد", Order = 3, Group = 1 };
            SaveAndCloseCommand = new UICommand(OnSaveAndClose, OnCanSave, ModifierKeys.Control | ModifierKeys.Alt, Key.S) { Caption = "حفظ وإغلاق", Order = 4, Group = 1 };
            DeleteCommand = new UICommand(OnDelete, OnCanDelete, ModifierKeys.Alt, Key.Delete) { Caption = "حذف", Order = 5, Group = 2 };
            MoveToFirstCommand = new UICommand(OnMoveToFirst, OnCanMoveUp, ModifierKeys.Control, Key.Home) { Caption = "السجل الأول", Order = 6, Group = 3 };
            MoveToPreviousCommand = new UICommand(OnMoveToPrevious, OnCanMoveUp, ModifierKeys.Control, Key.Left) { Caption = "السجل السابق", Order = 7, Group = 3 };
            MoveToNextCommand = new UICommand(OnMoveToNext, OnCanMoveDown, ModifierKeys.Control, Key.Right) { Caption = "السجل التالي", Order = 8, Group = 3 };
            MoveToLastCommand = new UICommand(OnMoveToLast, OnCanMoveDown, ModifierKeys.Control, Key.End) { Caption = "السجل الأخير", Order = 9, Group = 3 };
            CloseCommand = new UICommand(OnClose, ModifierKeys.Control, Key.F4) { Caption = "إغلاق", Order = 10, Group = 4 };
        }

        #region Core Logic

        /// <summary>
        /// Execute main flow of Saving Entity process.
        /// </summary>
        /// <returns>True, if save operation have been successed. False, otherwise.</returns>
        /// <exception cref="SaryaAccounting.Basis.ConstraintViolationException">Whether a constraint violation has happened in the database.</exception>
        /// <remarks>
        /// Steps of flow:
        ///     1. Validate
        ///     2. Save
        ///     3. Capture entity state.
        ///     4. Return the saving result.
        /// Handled Exception:
        ///     1. AccessDeniedSecurityException which is raised from the Domain Layer of the application.
        ///     2. ConstraintViolationException which is raised from the Database according to the constraint violation value.
        /// </remarks>
        protected virtual bool CoreSave()
        {
            bool retVal = false;

            try
            {
                Entity.Validate();
                if (Entity.IsValid())
                {
                    EntityObj.CloseSession();
                    EntityObj.Save(Entity);
                    Entity.Capture();
                    retVal = true;
                }
                else
                    retVal = false;
            }
            catch (ConstraintViolationException constVioEx)
            {
                MessageService.ShowExclamationMessage(constVioEx, "Duplicate value", true);
            }
            catch (AccessDeniedSecurityException secEx)
            {
                MessageService.ShowSecurityMessage(secEx, true);
            }

            return retVal;
        }

        /// <summary>
        /// 1. Close the view which is associated with this view model 
        /// 2. dispose the current view model.
        /// </summary>
        /// <remarks>
        /// 1- Remove (xxx)Editor UserControl from the RegionEditor.
        /// 2- Call Dispose method.
        /// </remarks>
        protected virtual void CoreClose()
        {
            object view = RegionManager.GetView(this);
            IRegion windowRegion = RegionManager.Regions[Constants.RegionEditor];
            windowRegion.Remove(view);

            this.Dispose();
        }

        /// <summary>
        /// Execute main flow of the Delete Entity process.
        /// </summary>
        /// <returns>True, if Delete operation have been successed. False, otherwise.</returns>
        protected virtual bool CoreDelete()
        {
            bool retVal = false;

            try
            {
                EntityObj.Delete(Entity);
                RefreshBrowser();
                retVal = true;
            }
            catch (AccessDeniedSecurityException secEx)
            {
                MessageService.ShowSecurityMessage(secEx, true);
            }

            return retVal;
        }

        /// <summary>
        /// Show the information of the passed Entity on the view controls.
        /// </summary>
        /// <param name="entity">Business entity to be shown on the view controls.</param>
        protected virtual void CorePopulateEntity(TCore entity)
        {
            Entity = entity;
        }

        #endregion

        #region Main Business Logic

        /// <summary>
        /// New Entity command handler.
        /// </summary>
        protected virtual void OnNew()
        {
            PreLeave();
            CorePopulateEntity(new TCore());

            UpdateStatus(Resources.New, EntityName);
        }

        /// <summary>
        /// Determine if Save button collection can be pressed.
        /// </summary>
        /// <returns>True, If Entity.IsValid. False, otherwise.</returns>
        protected virtual bool OnCanSave()
        {
            if (Entity == null)
                return false;
            else
                return Entity.IsValid();
        }

        /// <summary>
        /// Save current Entity command handler.
        /// </summary>
        /// <remarks>
        /// 1- CoreSave
        /// 2- RefreshBrowser
        /// 3- UpdateStatus
        /// </remarks>
        protected virtual void OnSave()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = CoreSave();

                RefreshBrowser();

                UpdateStatus(savingResult, Resources.SaveSucceed, Resources.SaveUnsucceed, EntityName);
            }
        }

        /// <summary>
        /// Save current Entity and Populate new Entity on the View.
        /// </summary>
        /// <remarks>
        /// 1- CoreSave
        /// 2- RefreshBrowser
        /// 3- CorePopulateEntity
        /// 4- UpdateStatus
        /// </remarks>
        protected virtual void OnSaveAndNew()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = CoreSave();

                RefreshBrowser();

                if (savingResult)
                    CorePopulateEntity(new TCore());

                UpdateStatus(savingResult, Resources.SaveAndNewSucceed, Resources.SaveUnsucceed, EntityName);
            }
        }

        /// <summary>
        /// Save current Entity and Close the Screen.
        /// </summary>
        /// <remarks>
        /// 1- CoreSave
        /// 2- RefreshBrowser
        /// 3- CoreClose
        /// 4- UpdateStatus
        /// </remarks>
        protected virtual void OnSaveAndClose()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = CoreSave();

                RefreshBrowser();

                if (savingResult)
                    CoreClose();

                UpdateStatus(savingResult, Resources.SaveAndCloseSucceed, Resources.SaveUnsucceed, EntityName);
            }
        }

        /// <summary>
        /// Close screen command handler.
        /// </summary>
        /// <remarks>
        /// 1- PreLeave
        /// 2- CoreClose
        /// </remarks>
        protected virtual void OnClose()
        {
            PreLeave();
            CoreClose();

            UpdateStatus(Resources.CloseEditor, EntityName);
        }

        /// <summary>
        /// Check about Entity changes and ask the use to save that changes or not.
        /// </summary>
        /// <remarks>
        /// 1- Checks if Entity has changed, and ask the user if he want to save that changes or not.
        /// 2- Refresh browser.
        /// 
        /// You have to call this method before changing the value of EditorViewModel.Entity Property.</remarks>
        protected virtual void PreLeave()
        {
            MessageServiceResultEnum userResult;

            if (Entity != null && Entity.IsStateChanged)
            {
                userResult = MessageService.RequestUserConfirmation("Do you want to save the modifications?", "Save Confirmation");

                if (userResult == MessageServiceResultEnum.Yes)
                    OnSave();               // تقوم بإنعاش شاشة الإستعراض ضمنياً
                else
                    RefreshBrowser();       // تعمل على نفس كائن شاشة الإستعراض لذلك يجب تحديث شاشة الإستعراض بكائنات جديدة في حال لم يتم الحفظ
            }
        }

        /// <summary>
        /// Determine if Delete button can be pressed or not.
        /// </summary>
        /// <returns></returns>
        protected virtual bool OnCanDelete()
        {
            return Entity != null && !Entity.IsEmpty;
        }

        /// <summary>
        /// Delete current Entity from the database.
        /// this method should be overriden in the derived class, to specify the User confirmation method about the delete.
        /// </summary>
        protected virtual void OnDelete()
        {
            MessageServiceResultEnum result = MessageService.RequestUserConfirmation(DeleteConfirmationMessage);

            if (result == MessageServiceResultEnum.Yes)
            {
                bool deletionResult = CoreDelete();
                CoreClose();
                UpdateStatus(deletionResult, Resources.DeleteSucceed, Resources.DeleteUnsucceed, EntityName);
            }
        }

        /// <summary>
        /// Determine if MoveToFirst, and MoveToPrevious can be pressed or not.
        /// </summary>
        /// <returns></returns>
        protected virtual bool OnCanMoveUp()
        {
            return !EntityNavigator.IsAtFirst;
        }

        /// <summary>
        /// Determine if MoveToNext, and MoveToLast can be pressed or not.
        /// </summary>
        /// <returns></returns>
        protected virtual bool OnCanMoveDown()
        {
            return !EntityNavigator.IsAtLast;
        }

        /// <summary>
        /// Move current Entity to the First Record in the BrowserScreen.
        /// </summary>
        /// <remarks>
        /// This method depend on IBusinessEntityNavigator interface.
        /// 
        /// 1- Ask user to save his changes.
        /// 2- Move to next entity.
        /// 3- Populate the new entity on the Screen.
        /// 4- Update View navigation controls.
        /// </remarks>
        protected virtual void OnMoveToFirst()
        {
            PreLeave();
            EntityNavigator.MoveToFirst();
            CorePopulateEntity(EntityNavigator.CurrentEntity);
            PostLeave();

            UpdateStatus(Resources.MoveToFirst, EntityName);
        }

        /// <summary>
        /// Move current Entity to the Previous Record in the BrowserScreen.
        /// </summary>
        /// <remarks>
        /// This method depend on IBusinessEntityNavigator interface.
        /// 
        /// 1- Ask user to save his changes.
        /// 2- Move to previous entity.
        /// 3- Populate the new entity on the Screen.
        /// 4- Update View navigation controls.
        /// </remarks>
        protected virtual void OnMoveToPrevious()
        {
            PreLeave();
            EntityNavigator.MoveToPrevious();
            CorePopulateEntity(EntityNavigator.CurrentEntity);
            PostLeave();

            UpdateStatus(Resources.MoveToPrevious, EntityName);
        }

        /// <summary>
        /// Move current Entity to the Next Record in the BrowserScreen.
        /// </summary>
        /// <remarks>
        /// This method depend on IBusinessEntityNavigator interface.
        /// 
        /// 1- Ask user to save his changes.
        /// 2- Move to next entity.
        /// 3- Populate the new entity on the Screen.
        /// 4- Update View navigation controls.
        /// </remarks>
        protected virtual void OnMoveToNext()
        {
            PreLeave();
            EntityNavigator.MoveToNext();
            CorePopulateEntity(EntityNavigator.CurrentEntity);
            PostLeave();

            UpdateStatus(Resources.MoveToNext, EntityName);
        }

        /// <summary>
        /// Move current Entity to the Last Record in the BrowserScreen.
        /// </summary>
        /// <remarks>
        /// This method depend on IBusinessEntityNavigator interface.
        /// 
        /// 1- Ask user to save his changes.
        /// 2- Move to last entity.
        /// 3- Populate the new entity on the Screen.
        /// 4- Update View navigation controls.
        /// </remarks>
        protected virtual void OnMoveToLast()
        {
            PreLeave();
            EntityNavigator.MoveToLast();
            CorePopulateEntity(EntityNavigator.CurrentEntity);
            PostLeave();

            UpdateStatus(Resources.MoveToLast, EntityName);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Notify the browser viewModel to take the step of refresh the browser.
        /// </summary>
        protected void RefreshBrowser()
        {
            EventAggregator.GetEvent<BrowserRefreshEvent<TCore>>().Publish(Entity);
        }

        #endregion

        #region Overriden Methods

        /// <summary>
        /// 1. Retrive EntityParam from the navigationContext
        /// 2. Assign it the Entity Property.
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <remarks>
        /// Occurs at the first open time
        /// يحدث عندما يتم فتح شاشة التعديل لأول مرة</remarks>
        public override void OnNavigatedTo(NavigationContext navigationContext)
        {
            OnPostShow(navigationContext);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <param name="continuationCallback"></param>
        /// <remarks>يحدث عندما يتم طلب عرض NavigateTo هذه الشاشة مرة آخرى بعد المرة الأول</remarks>
        public override void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            OnPostShow(navigationContext);
        }

        /// <summary>
        /// Called after navigation came to the viewModel
        /// </summary>
        /// <param name="navigationContext"></param>
        protected virtual void OnPostShow(NavigationContext navigationContext)
        {
            PreLeave();
            CorePopulateEntity(navigationContext.GetParameter<TCore>(Constants.EntityParam) ?? new TCore());
            PostLeave();

            UpdateStatus(Resources.ReopenEditorScreen, EntityName);
        }

        public override void Dispose()
        {
            EntityObj.CloseSession();
        }

        #endregion

        #region IDataErrorInfo Members

        string IDataErrorInfo.Error
        {
            get { return Entity.Error; }
        }

        string IDataErrorInfo.this[string columnName]
        {
            get { return Entity[columnName]; }
        }

        #endregion

        #region Event Handlers

        private void Entity_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)SaveAndNewCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)SaveAndCloseCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)DeleteCommand).RaiseCanExecuteChanged();
        }

        private void PostLeave()
        {
            ((DelegateCommand)MoveToFirstCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)MoveToLastCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)MoveToNextCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)MoveToPreviousCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)DeleteCommand).RaiseCanExecuteChanged();
        }

        #endregion
    }
}
